package com.zhihao.trigger.job;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zhihao.domain.task.model.entity.TaskEntity;
import com.zhihao.domain.task.service.ITaskService;
import io.micrometer.core.annotation.Timed;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Jackson
 * @description 发送MQ消息任务队列( task-mq任务补偿表,适用于send_award奖品发放消息mq、send_rebate行为返利订单mq)
 * @create 2024-04-06 10:47
 */
@Slf4j
@Component()
public class SendMessageTaskJob {

    @Resource
    private ITaskService taskService;
    @Resource
    private ThreadPoolExecutor executor;
    @Resource
    private IDBRouterStrategy dbRouter;

    @Resource
    private RedissonClient redisson;

    /**
     * 本地化任务注解 @Scheduled(cron = "0/5 * * * * ?")
     * 分布式任务注解 @XxlJob("SendMessageTaskJob_DB01") - DB01分库的task补偿定时任务
     */
    /*
        @Timed:

        @XxlJob:

     */
    @Timed(value = "SendMessageTaskJob_DB1", description = "发送MQ消息任务队列")
    @XxlJob("SendMessageTaskJob_DB1")
    public void exec_db01() {
        /*
            使用xxl-job作为分布式任务调度框架后，为防止分布式架构下多个节点存在重复执行定时任务的问题，所以需要加分布式锁
            reason：
                分布式应用N台机器部署互备，任务调度会有N个同时执行，那么这里需要增加“抢占锁机制”，谁抢占到就执行，下一轮继续抢占
         */
        RLock lock = redisson.getLock("big-market-SendMessageTaskJob_DB01");
        boolean isLocked = false;
        try {
            /*
                waitTime 最大等待时间：
                    线程会尝试在 3秒内 不断尝试获取锁。如果在3秒内成功获取锁，返回 true；如果超过3秒仍未获取到锁，则放弃并返回 false。
                leaseTime 锁的自动释放时间(租约时间)：
                    如果成功获取锁且 leaseTime > 0：锁会在 leaseTime 秒后自动释放。
                    如果你使用的是 Redisson 的分布式锁（RLock），leaseTime=0 表示 不自动释放锁，必须显式调用 unlock()。此时如果忘记释放，会导致死锁！
             */
            isLocked = lock.tryLock(3, 0, TimeUnit.SECONDS);
            if (!isLocked) return;

            // 设置库表
            dbRouter.setDBKey(1);
            dbRouter.setTBKey(0);
            // 查询未发送的任务
            List<TaskEntity> taskEntities = taskService.queryNoSendMessageTaskList();
            if (taskEntities.isEmpty()) return;
            // 发送MQ消息
            for (TaskEntity taskEntity : taskEntities) {
                try {
                    taskService.sendMessage(taskEntity);
                    taskService.updateTaskSendMessageCompleted(taskEntity.getUserId(), taskEntity.getMessageId());
                } catch (Exception e) {
                    log.error("定时任务，发送MQ消息失败 userId: {} topic: {}", taskEntity.getUserId(), taskEntity.getTopic());
                    taskService.updateTaskSendMessageFail(taskEntity.getUserId(), taskEntity.getMessageId());
                }
            }
        } catch (Exception e) {
            log.error("定时任务，扫描MQ任务表发送消息失败。", e);
        } finally {
            dbRouter.clear();
            if (isLocked){
                lock.unlock();
            }
        }
    }

    @Timed(value = "SendMessageTaskJob_DB2", description = "发送MQ消息任务队列")
    @XxlJob("SendMessageTaskJob_DB2")
    public void exec_db02() {
        RLock lock = redisson.getLock("big-market-SendMessageTaskJob_DB02");
        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(3, 0, TimeUnit.SECONDS);
            if (!isLocked) return;

            // 设置库表
            dbRouter.setDBKey(2);
            dbRouter.setTBKey(0);
            // 查询未发送的任务
            List<TaskEntity> taskEntities = taskService.queryNoSendMessageTaskList();
            if (taskEntities.isEmpty()) return;
            // 发送MQ消息
            for (TaskEntity taskEntity : taskEntities) {
                try {
                    taskService.sendMessage(taskEntity);
                    taskService.updateTaskSendMessageCompleted(taskEntity.getUserId(), taskEntity.getMessageId());
                } catch (Exception e) {
                    log.error("定时任务，发送MQ消息失败 userId: {} topic: {}", taskEntity.getUserId(), taskEntity.getTopic());
                    taskService.updateTaskSendMessageFail(taskEntity.getUserId(), taskEntity.getMessageId());
                }
            }
        } catch (Exception e) {
            log.error("定时任务，扫描MQ任务表发送消息失败。", e);
        } finally {
            dbRouter.clear();
            if (isLocked){
                lock.unlock();
            }
        }
    }


    /*
        task表-分库-每个库1张表
     */
//    @Scheduled(cron = "0/5 * * * * ?")
//    public void exec() {
//        try {
//            // 获取分库数量
//            int dbCount = dbRouter.dbCount();
//
//            // 逐个库扫描表【每个库一个任务表】
//            for (int dbIdx = 1; dbIdx <= dbCount; dbIdx++) {
//                int finalDbIdx = dbIdx;
//                // 线程池
//                executor.execute(() -> {
//                    try {
//                        dbRouter.setDBKey(finalDbIdx);
//                        dbRouter.setTBKey(0);
//                        List<TaskEntity> taskEntities = taskService.queryNoSendMessageTaskList();
//                        if (taskEntities.isEmpty()) return;
//                        // 发送MQ消息---每次10条，这里可以使用线程池，分10个线程运行，而不是使用一个单一线程循环10次
//                        for (TaskEntity taskEntity : taskEntities) {
//                            // 开启线程发送，提高发送效率。配置的线程池策略为 CallerRunsPolicy，在 ThreadPoolConfig 配置中有4个策略，面试中容易对比提问。可以检索下相关资料。
//                            executor.execute(() -> {
//                                try {
//                                    taskService.sendMessage(taskEntity);
//                                    taskService.updateTaskSendMessageCompleted(taskEntity.getUserId(), taskEntity.getMessageId());
//                                } catch (Exception e) {
//                                    log.error("定时任务，发送MQ消息失败 userId: {} topic: {}", taskEntity.getUserId(), taskEntity.getTopic());
//                                    taskService.updateTaskSendMessageFail(taskEntity.getUserId(), taskEntity.getMessageId());
//                                }
//                            });
//                        }
//                    } finally {
//                        dbRouter.clear();
//                    }
//                });
//            }
//        } catch (Exception e) {
//            log.error("定时任务，扫描MQ任务表发送消息失败。", e);
//        } finally {
//            dbRouter.clear();
//        }
//    }

}
